﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using System.Data;
using System.Diagnostics;
using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace NGenericDimensions
{

    public interface IArea : IDimension
    {
        Lengths.LengthUnitOfMeasure UnitOfMeasure { get; }
    }

    public interface IArea<out TUnitOfMeasure> : IArea where TUnitOfMeasure : Lengths.LengthUnitOfMeasure
    {
    }

    public struct Area<TUnitOfMeasure, TDataType> : IArea<TUnitOfMeasure>
        where TUnitOfMeasure : Lengths.LengthUnitOfMeasure
        where TDataType : struct, IComparable, IConvertible
    {


        private TDataType mArea;
        public Area(TDataType area)
        {
            mArea = area;
        }

        public Area(Area<TUnitOfMeasure, TDataType> area)
        {
            mArea = area.mArea;
        }

        public Area(IArea areaToConvertFrom)
        {
            mArea = (TDataType)(object)(areaToConvertFrom.Value * (areaToConvertFrom.UnitOfMeasure.GetCompleteMultiplier<TUnitOfMeasure>(2)));
        }

        [EditorBrowsable(EditorBrowsableState.Always)]
        public TDataType AreaValue
        {
            get { return mArea; }
        }

        [EditorBrowsable(EditorBrowsableState.Advanced)]
        public TUnitOfMeasure UnitOfMeasure
        {
            get { return UnitOfMeasureGlobals<TUnitOfMeasure>.GlobalInstance; }
        }

        private Lengths.LengthUnitOfMeasure UnitOfMeasure1
        {
            get { return UnitOfMeasure; }
        }
        Lengths.LengthUnitOfMeasure IArea.UnitOfMeasure
        {
            get { return UnitOfMeasure1; }
        }

        [EditorBrowsable(EditorBrowsableState.Always)]
        public Area<TNewUnitOfMeasure, TNewDataType> ConvertTo<TNewUnitOfMeasure, TNewDataType>()
            where TNewUnitOfMeasure : Lengths.LengthUnitOfMeasure
            where TNewDataType : struct, IComparable, IConvertible
        {
            return (TNewDataType)(object)(ValueAsDouble * (UnitOfMeasure1.GetCompleteMultiplier<TNewUnitOfMeasure>(2)));
        }

        [EditorBrowsable(EditorBrowsableState.Always)]
        public Area<TUnitOfMeasure, TNewDataType> ConvertTo<TNewDataType>() where TNewDataType : struct, IComparable, IConvertible
        {
            return (TNewDataType)(object)mArea;
        }

        public static implicit operator Area<TUnitOfMeasure, TDataType>(TDataType area)
        {
            return new Area<TUnitOfMeasure, TDataType>(area);
        }

        public static explicit operator TDataType(Area<TUnitOfMeasure, TDataType> area)
        {
            return area.mArea;
        }

        public static Area<TUnitOfMeasure, TDataType> operator +(Area<TUnitOfMeasure, TDataType> area1, Area<TUnitOfMeasure, TDataType> area2)
        {
            return new Area<TUnitOfMeasure, TDataType>(Math.GenericOperatorMath<TDataType>.Add(area1.mArea, area2.mArea));
        }

        public static Area<TUnitOfMeasure, double> operator +(Area<TUnitOfMeasure, TDataType> area1, IArea area2)
        {
            return area1.ValueAsDouble + (area2.Value * (area2.UnitOfMeasure.GetCompleteMultiplier<TUnitOfMeasure>(2)));
        }

        public static Area<TUnitOfMeasure, TDataType> operator -(Area<TUnitOfMeasure, TDataType> area1, Area<TUnitOfMeasure, TDataType> area2)
        {
            return new Area<TUnitOfMeasure, TDataType>(Math.GenericOperatorMath<TDataType>.Subtract(area1.mArea, area2.mArea));
        }

        public static Area<TUnitOfMeasure, double> operator -(Area<TUnitOfMeasure, TDataType> area1, IArea area2)
        {
            return area1.ValueAsDouble - (area2.Value * (area2.UnitOfMeasure.GetCompleteMultiplier<TUnitOfMeasure>(2)));
        }

        public static Area<TUnitOfMeasure, TDataType> operator *(TDataType area1, Area<TUnitOfMeasure, TDataType> area2)
        {
            return new Area<TUnitOfMeasure, TDataType>(Math.GenericOperatorMath<TDataType>.Multiply(area1, area2.mArea));
        }

        public static Area<TUnitOfMeasure, TDataType> operator *(Area<TUnitOfMeasure, TDataType> area1, TDataType area2)
        {
            return new Area<TUnitOfMeasure, TDataType>(Math.GenericOperatorMath<TDataType>.Multiply(area1.mArea, area2));
        }

        public static Area<TUnitOfMeasure, double> operator /(Area<TUnitOfMeasure, TDataType> area1, double area2)
        {
            return new Area<TUnitOfMeasure, double>(Convert.ToDouble((object)area1.mArea) / area2);
        }

        public static double operator /(Area<TUnitOfMeasure, TDataType> area1, Area<TUnitOfMeasure, TDataType> area2)
        {
            return area1.ValueAsDouble / area2.ValueAsDouble;
        }

        public static bool operator ==(Area<TUnitOfMeasure, TDataType> area1, Area<TUnitOfMeasure, TDataType> area2)
        {
            return area1.mArea.CompareTo(area2.mArea) == 0;
        }

        public static bool operator ==(Area<TUnitOfMeasure, TDataType> area1, IArea area2)
        {
            return area1.ValueAsDouble.CompareTo(area2.Value * (area2.UnitOfMeasure.GetCompleteMultiplier<TUnitOfMeasure>(2))) == 0;
        }

        public static bool operator !=(Area<TUnitOfMeasure, TDataType> area1, Area<TUnitOfMeasure, TDataType> area2)
        {
            return area1.mArea.CompareTo(area2.mArea) != 0;
        }

        public static bool operator !=(Area<TUnitOfMeasure, TDataType> area1, IArea area2)
        {
            return area1.ValueAsDouble.CompareTo(area2.Value * (area2.UnitOfMeasure.GetCompleteMultiplier<TUnitOfMeasure>(2))) != 0;
        }

        public static bool operator >(Area<TUnitOfMeasure, TDataType> area1, Area<TUnitOfMeasure, TDataType> area2)
        {
            return area1.mArea.CompareTo(area2.mArea) > 0;
        }

        public static bool operator >(Area<TUnitOfMeasure, TDataType> area1, IArea area2)
        {
            return area1.ValueAsDouble.CompareTo(area2.Value * (area2.UnitOfMeasure.GetCompleteMultiplier<TUnitOfMeasure>(2))) > 0;
        }

        public static bool operator <(Area<TUnitOfMeasure, TDataType> area1, Area<TUnitOfMeasure, TDataType> area2)
        {
            return area1.mArea.CompareTo(area2.mArea) < 0;
        }

        public static bool operator <(Area<TUnitOfMeasure, TDataType> area1, IArea area2)
        {
            return area1.ValueAsDouble.CompareTo(area2.Value * (area2.UnitOfMeasure.GetCompleteMultiplier<TUnitOfMeasure>(2))) < 0;
        }

        public static bool operator >=(Area<TUnitOfMeasure, TDataType> area1, Area<TUnitOfMeasure, TDataType> area2)
        {
            return area1.mArea.CompareTo(area2.mArea) >= 0;
        }

        public static bool operator >=(Area<TUnitOfMeasure, TDataType> area1, IArea area2)
        {
            return area1.ValueAsDouble.CompareTo(area2.Value * (area2.UnitOfMeasure.GetCompleteMultiplier<TUnitOfMeasure>(2))) >= 0;
        }

        public static bool operator <=(Area<TUnitOfMeasure, TDataType> area1, Area<TUnitOfMeasure, TDataType> area2)
        {
            return area1.mArea.CompareTo(area2.mArea) <= 0;
        }

        public static bool operator <=(Area<TUnitOfMeasure, TDataType> area1, IArea area2)
        {
            return area1.ValueAsDouble.CompareTo(area2.Value * (area2.UnitOfMeasure.GetCompleteMultiplier<TUnitOfMeasure>(2))) <= 0;
        }

        private double ValueAsDouble
        {
            get { return Convert.ToDouble((object)mArea); }
        }
        double IDimension.Value
        {
            get { return ValueAsDouble; }
        }

    }

    [EditorBrowsable(EditorBrowsableState.Never)]
    public struct LengthVolumeExtension<T> where T : struct, IComparable, IConvertible
    {
        [EditorBrowsable(EditorBrowsableState.Never)]
        public T Volume;
    }
}

namespace NGenericDimensions.Extensions
{

    public static class AreaExtensionMethods
    {

        [EditorBrowsable(EditorBrowsableState.Always)]
        public static TDataType AreaValue<TUnitOfMeasure, TDataType>(this Nullable<Area<TUnitOfMeasure, TDataType>> area)
            where TUnitOfMeasure : Lengths.LengthUnitOfMeasure, new()
            where TDataType : struct, IComparable, IConvertible
        {
            return area.Value.AreaValue;
        }

    }

}